package class12;

import java.util.HashMap;

public class Code01_MoneyProblem {

	public static long func1(int[] d, int[] p) {
		return process(d, p, 0, 0);
	}

	// int[] d d[i]：i号怪兽的武力
	// int[] p p[i]：i号怪兽要求的钱
	// hp 当前你所具有的能力
	// index 来到了第index个怪兽的面前

	// 目前，你的能力是hp，你来到了index号怪兽的面前，如果要通过后续所有的怪兽，
	// 请返回需要花的最少钱数
	public static long process(int[] d, int[] p, int hp, int index) {
		if (index == d.length) { // base case
			return 0;
		}
		// index < d.length 还有怪兽要面对
		if (hp < d[index]) {
			return p[index] + process(d, p, hp + d[index], index + 1);
		} else { // hp >= d[index] 可以贿赂，也可以不贿赂
			return Math.min(p[index] + process(d, p, hp + d[index], index + 1),

					process(d, p, hp, index + 1));
		}
	}

	// 正数数组 d p
	public static int dp1(int[] d, int[] p) {
		if (d == null || d.length == 0) {
			return 0;
		}

		int sum = 0;
		for (int ability : d) {
			sum += ability;
		}
		int N = d.length;

		int[][] dp = new int[N + 1][sum + 1];

		// dp[N][...] = 0;
		for (int i = N - 1; i >= 0; i--) {
			for (int j = 0; j <= sum; j++) {
				// j 能力 i 怪兽号
				if (j + d[i] > sum) {
					continue;
				}
				if (j < d[i]) {
					dp[i][j] = p[i] + dp[i + 1][j + d[i]];
				} else {
					dp[i][j] = Math.min(p[i] + dp[i + 1][j + d[i]], dp[i + 1][j]);
				}
			}
		}

		return dp[0][0];

	}

	public static long func2(int[] d, int[] p) {
		int sum = 0;
		for (int num : d) {
			sum += num;
		}
		long[][] dp = new long[d.length + 1][sum + 1];
		for (int i = 0; i <= sum; i++) {
			dp[0][i] = 0;
		}
		for (int cur = d.length - 1; cur >= 0; cur--) {
			for (int hp = 0; hp <= sum; hp++) {
				// 如果这种情况发生，那么这个hp必然是递归过程中不会出现的状态
				// 既然动态规划是尝试过程的优化，尝试过程碰不到的状态，不必计算
				if (hp + d[cur] > sum) {
					continue;
				}
				if (hp < d[cur]) {
					dp[cur][hp] = p[cur] + dp[cur + 1][hp + d[cur]];
				} else {
					dp[cur][hp] = Math.min(p[cur] + dp[cur + 1][hp + d[cur]], dp[cur + 1][hp]);
				}
			}
		}
		return dp[0][0];
	}
	
	
	
	public static long func1dp(int[] d, int[] p) {
		HashMap<String,Long> dp = new HashMap<>();
		return processdp(d, p, 0, 0, dp);
	}

	// int[] d d[i]：i号怪兽的武力
	// int[] p p[i]：i号怪兽要求的钱
	// hp 当前你所具有的能力
	// index 来到了第index个怪兽的面前

	// 目前，你的能力是hp，你来到了index号怪兽的面前，如果要通过后续所有的怪兽，
	// 请返回需要花的最少钱数
	public static long processdp(int[] d, int[] p, int hp, int index,HashMap<String,Long> dp) {
		String key = String.valueOf(hp) + "_" + String.valueOf(index);
		if(dp.containsKey(key)) {
			return dp.get(key);
		}
		long ans = 0;
		if(index < d.length) {
			if (hp < d[index]) {
				ans = p[index] + process(d, p, hp + d[index], index + 1);
			} else { // hp >= d[index] 可以贿赂，也可以不贿赂
				ans = Math.min(p[index] + process(d, p, hp + d[index], index + 1),

						process(d, p, hp, index + 1));
			}
		}
		dp.put(key, ans);
		return ans;

	}
	

	public static long func3(int[] d, int[] p) {
		int sum = 0;
		for (int num : p) {
			sum += num;
		}
		// dp[i][j]含义：
		// 能经过0～i的怪兽，且花钱为j（花钱的严格等于j）时的武力值最大是多少？
		// 如果dp[i][j]==-1，表示经过0～i的怪兽，花钱为j是无法通过的，或者之前的钱怎么组合也得不到正好为j的钱数
		int[][] dp = new int[d.length][sum + 1];
		for (int i = 0; i < dp.length; i++) {
			for (int j = 0; j <= sum; j++) {
				dp[i][j] = -1;
			}
		}
		// 经过0～i的怪兽，花钱数一定为p[0]，达到武力值d[0]的地步。其他第0行的状态一律是无效的
		dp[0][p[0]] = d[0];
		for (int i = 1; i < d.length; i++) {
			for (int j = 0; j <= sum; j++) {
				// 可能性一，为当前怪兽花钱
				// 存在条件：
				// j - p[i]要不越界，并且在钱数为j - p[i]时，要能通过0～i-1的怪兽，并且钱数组合是有效的。
				if (j >= p[i] && dp[i - 1][j - p[i]] != -1) {
					dp[i][j] = dp[i - 1][j - p[i]] + d[i];
				}
				// 可能性二，不为当前怪兽花钱
				// 存在条件：
				// 0~i-1怪兽在花钱为j的情况下，能保证通过当前i位置的怪兽
				if (dp[i - 1][j] >= d[i]) {
					// 两种可能性中，选武力值最大的
					dp[i][j] = Math.max(dp[i][j], dp[i - 1][j]);
				}
			}
		}
		int ans = 0;
		// dp表最后一行上，dp[N-1][j]代表：
		// 能经过0～N-1的怪兽，且花钱为j（花钱的严格等于j）时的武力值最大是多少？
		// 那么最后一行上，最左侧的不为-1的列数(j)，就是答案
		for (int j = 0; j <= sum; j++) {
			if (dp[d.length - 1][j] != -1) {
				ans = j;
				break;
			}
		}
		return ans;
	}

	public static int[][] generateTwoRandomArray(int len, int value) {
		int size = (int) (Math.random() * len) + 1;
		int[][] arrs = new int[2][size];
		for (int i = 0; i < size; i++) {
			arrs[0][i] = (int) (Math.random() * value) + 1;
			arrs[1][i] = (int) (Math.random() * value) + 1;
		}
		return arrs;
	}

	public static void main(String[] args) {
		int len = 10;
		int value = 20;
		int testTimes = 1000000;
		for (int i = 0; i < testTimes; i++) {
			int[][] arrs = generateTwoRandomArray(len, value);
			int[] d = arrs[0];
			int[] p = arrs[1];
			long ans1 = func1(d, p);
			long ans2 = func2(d, p);
			long ans3 = func3(d, p);
			if (ans1 != ans2 || ans2 != ans3) {
				System.out.println("oops!");
			}
		}

	}

}
